package pl.edu.agh.strategies;

import org.apache.log4j.Logger;
import org.jgap.InvalidConfigurationException;
import org.jgap.gp.GPProblem;
import org.jgap.gp.IGPProgram;
import org.jgap.gp.impl.GPConfiguration;
import org.jgap.gp.impl.GPGenotype;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import pl.edu.agh.gp.GpPopulationListener;
import pl.edu.agh.gp.StockGPConfiguration;
import pl.edu.agh.model.core.Price;
import pl.edu.agh.gp.fitness.OverBHFitnessFunction;

import java.util.List;

import static org.jgap.event.GeneticEvent.GPGENOTYPE_EVOLVED_EVENT;

@Component
public class GPStrategy extends GPProblem implements Strategy {
    private Logger logger = Logger.getLogger(GPStrategy.class);

    private @Autowired StockGPConfiguration stockGPConfiguration;
    private @Autowired OverBHFitnessFunction fitnessFunc;
    private IGPProgram allTimeBest;
    private double bestFitness;
    private double totalWon;

    public GPGenotype create() throws InvalidConfigurationException {
        GPConfiguration conf = getGPConfiguration();
        return stockGPConfiguration.create(conf);
    }

    public List<Price> play(double initialMoney, List<Price> prices) {
        long startTime = System.nanoTime();

        logger.info("GP play started for " + initialMoney + " and " + prices.size() + " prices");
        fitnessFunc.setInitialMoney(initialMoney);
        fitnessFunc.setPrices(prices);

        try {
            GPConfiguration gpConf = stockGPConfiguration.getConfiguration(fitnessFunc);
            gpConf.getEventManager().addEventListener(GPGENOTYPE_EVOLVED_EVENT, new GpPopulationListener(fitnessFunc));
            setGPConfiguration(gpConf);

            GPGenotype gp = stockGPConfiguration.create(gpConf);
            fitnessFunc.setVariables(stockGPConfiguration.getVariables());

            gp.evolve(stockGPConfiguration.getEvolutionsNo());

            allTimeBest = gp.getAllTimeBest();
            bestFitness = allTimeBest.getFitnessValue();

            if(allTimeBest.getFitnessValue() == 0.0){
                logger.info("No GP program better than B&H found. Picking the worse one.");
                allTimeBest = fitnessFunc.getBestProgram();
                bestFitness = fitnessFunc.getBestFitness();
            }

            if(allTimeBest == null){
                throw new IllegalStateException("No GP program worse than B&H found that makes sense.");
            }

            logger.info("Printing best solution...");
            logger.info(allTimeBest.toStringNorm(0));
            logger.info("Fitness:" + bestFitness);

        } catch (InvalidConfigurationException e) {
            e.printStackTrace();
        }

        List<Price> income = fitnessFunc.getIncome(allTimeBest);
        totalWon = income.get(income.size() - 1).getIncome() - initialMoney;

        logger.info("GP play lasted: " + (System.nanoTime() - startTime) / Math.pow(10.0, 9) + " seconds.");
        logger.info("Total won: " + totalWon);

        return income;
    }

    public void setStockGPConfiguration(StockGPConfiguration stockGPConfiguration) {
        this.stockGPConfiguration = stockGPConfiguration;
    }

    public String getAllTimeBest() {
        return allTimeBest.toStringNorm(0);
    }

    public double getBestFitness() {
        return bestFitness;
    }

    @Override
    public double getTotalWon() {
        return totalWon;
    }
}
